package Game;

import java.util.Set;

public class Player extends Entity {
	private Vector mAccel;
	private Vector mVelocity;
	private Position mCurrent;
	private Position mNext;
	private boolean mIsFalling;
	private boolean mIsOnFloor;
	private boolean mIsDead;
	
	private boolean jumpInit = false;
	
	private static final int JUMP_STRENGTH = -5;
	private static final int PLAYER_COLLISION_MASK_LENGTH = 10;
	private static final double GRAVITY_STRENGTH = 0.25;
	
	enum CollisionType {
		Side, Top, None, Below
	}
	
	public Player(GameModel gameModel, Position initial) {
		super(gameModel);
		
		mCurrent = new Position(initial);
		mNext = new Position(initial);
		mVelocity = new Vector();
		mAccel = new Vector();
		mIsFalling = false;
		mIsOnFloor = false;
		mIsDead = false;
	}
	
	public boolean isDead() {
		return mIsDead;
	}
	
	public void gameLoop(double dt) {
		//check if colliding with a wall due to running into it, if yes you die!
		
		ApplyPhysics(dt);
		TestCollisions();
		UpdatePosition();
	}
	
	private void ApplyPhysics(double dt) {
	    Vector velocityAccel = new Vector(mVelocity);
	    Vector accelAccel = new Vector(mAccel);
	    
	    Vector accelVelocity = new Vector(mAccel);
	
        // SET accel to 0
        mAccel.set(0, 0);

	    // SET mNext to the current position
	    mNext.set(mCurrent);
        
        // SET the acceleration to the gravity
	    mAccel = new Vector(0, GRAVITY_STRENGTH);

        // Update the velocity vector
        UpdateVelocity(mAccel);
        
        // vel0*dt 
        velocityAccel.multiplyBy(dt);
        
        // 0.5*a*dt*dt
        accelAccel.multiplyBy(0.5*dt*dt);
        
        // a*dt
        accelVelocity.multiplyBy(dt);
        
        // vel0*dt + 0.5*a*dt*dt
        accelAccel.addBy(velocityAccel);

	    // pos = pos0 + vel0*dt + 0.5*a*dt*dt
        mNext.addBy(accelAccel);
	
	    // vel = vel + a*dt
        mVelocity.addBy(accelVelocity);
        
        //
        if (jumpInit) {
        	mVelocity.SetY(JUMP_STRENGTH);
        	jumpInit = false;
        }
    }

    private void UpdateVelocity(Vector accel) {
        int xVel = mVelocity.getRoundedX();
        int yVel = mVelocity.getRoundedY();

        // IF the rounded X velocity and accelerate is 0, and the unrounded one isn't
        if (xVel == 0 && mVelocity.getX() != 0 && mAccel.getX() == 0) {
            // SET the X velocity to 0
            mVelocity.SetX(0);
        }

        // IF the rounded Y velocity and accelerate is 0, and the unrounded one isn't
        if (yVel == 0 && mVelocity.getY() != 0 && mAccel.getY() == 0) {
            // SET the Y velocity to 0
            mVelocity.SetY(0);
        }
    }
    
    private void TestCollisions() {
    	Set<Platform> platforms = gameModel.getPlatforms();
    	int deltaY = mCurrent.getRoundedY() - mNext.getRoundedY();
    	
    	mIsFalling = (mVelocity.getY() > 0);
    	
    	if (deltaY != 0) {
    		if (mIsFalling && mIsOnFloor) {
        		mIsOnFloor = false;
        	}
    		
    		for (Platform plat : platforms) {
        		CollisionType type = DetectCollision(plat);
        		
        		if (type == CollisionType.Top) {
        			if (mIsFalling) {
        				mIsOnFloor = true;
        			}
        			
        			mVelocity.SetY(0);
        			mNext.SetY(mCurrent.getRoundedY());
        		}
        		else if (type == CollisionType.Side) {
        			mIsDead = true;
        		}
        	}
    	}
    	
    	if (mNext.getRoundedY() >= 400) {
    		mIsDead = true;
    	}
    }
    
    private CollisionType DetectCollision(Platform plat) {
    	CollisionType val = CollisionType.None;
    	
    	if (inXBounds(plat, mNext) && inYBounds(plat, mNext)) {
    		val = CollisionType.Top;
    		
    		if (sideDetection(plat) || (inXBounds(plat, mCurrent) && inYBounds(plat, mCurrent))) {
    			val = CollisionType.Side;
    		}
    	}
    	
		return val;
    }
    
    private boolean sideDetection(Platform plat) {
    	return mNext.getRoundedX() + PLAYER_COLLISION_MASK_LENGTH - 1 == plat.getPosition().getRoundedX();
    }
    
    private boolean inYBounds(Platform plat, Position curr) {
    	boolean topBounds =  (curr.getRoundedY() + PLAYER_COLLISION_MASK_LENGTH > plat.getPosition().getRoundedY());
    	boolean bottomBounds = (curr.getRoundedY() < plat.getPosition().getRoundedY() + plat.getHeight());
    	
    	return topBounds && bottomBounds;
    }
    
    private boolean inXBounds(Platform plat, Position curr) {
    	boolean leftBounds =  (curr.getRoundedX() + PLAYER_COLLISION_MASK_LENGTH > plat.getPosition().getRoundedX());
    	boolean rightBounds = (curr.getRoundedX() < plat.getPosition().getRoundedX() + plat.getWidth());
    	
    	return leftBounds && rightBounds;
    }
    
    private void UpdatePosition() {
    	mCurrent.set(mNext);
    }
    
    public Position getPosition() {
    	return mCurrent;
    }
    
    public int getLength() {
    	return PLAYER_COLLISION_MASK_LENGTH;
    }
    
    public boolean isOnFloor() {
    	return mIsOnFloor;
    }
    
    public boolean isFalling() {
    	return mIsFalling;
    }
    
    public void jump() {
    	if (mIsOnFloor) {
    		jumpInit = true;
    		mIsOnFloor = false;
    	}
    }
}
